package com.piaohailin.redis;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import java.nio.ByteBuffer;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author piaohailin@163.com
 * @date 2021/9/16
 */
@Slf4j
@RequiredArgsConstructor
@Component
public class RedisUtil {
    public static final String LOCK_PREFIX = "REDIS:LOCK:";
    /**
     * 单位ms
     **/
    public static final int LOCK_EXPIRE = 300;

    private final RedisTemplate<String, Object> redisTemplate;

    /**
     * 判断redis是否开启
     */
    public boolean ping() {
        boolean resultFlag = true;
        String pong = "";
        try {
            pong = redisTemplate.getConnectionFactory().getConnection().ping();
            if (!"PONG".equals(pong)) {
                resultFlag = false;
            }
        } catch (Exception e) {
            log.debug("Redis的心跳检测线异常：" + e.getMessage(), e);
            resultFlag = false;
        }
        return resultFlag;
    }

    private static byte[] toByteArray(long value) {
        return ByteBuffer.allocate(Long.SIZE / Byte.SIZE).putLong(value).array();
    }

    private static long byteArrayToLong(byte[] bytes) {
        ByteBuffer buffer = ByteBuffer.allocate(8);
        buffer.put(bytes, 0, bytes.length);
        buffer.flip();
        return buffer.getLong();
    }

    /**
     * 最终加强分布式锁
     *
     * @param key key值
     * @return 是否获取到
     */
    public Boolean lock(String key) {
        String lock = LOCK_PREFIX + key;
        // 利用lambda表达式
        return (Boolean) redisTemplate.execute((RedisCallback<Object>) connection -> {
            long expireAt = System.currentTimeMillis() + LOCK_EXPIRE + 1;
            Boolean acquire = connection.setNX(lock.getBytes(), toByteArray(expireAt));
            if (acquire == null) {
                return false;
            }
            // 验锁
            if (acquire) {
                return true;
            } else {
                byte[] value = connection.get(lock.getBytes());
                if (Objects.nonNull(value) && value.length > 0) {
                    long expireTime = byteArrayToLong(value);
                    if (expireTime < System.currentTimeMillis()) {
                        // 如果锁已经过期
                        byte[] oldValue = connection.getSet(lock.getBytes(), toByteArray(System.currentTimeMillis() + LOCK_EXPIRE + 1));
                        // 防止死锁
                        return byteArrayToLong(oldValue) < System.currentTimeMillis();
                    }
                }
            }
            return false;
        });
    }

    public Boolean unlock(String key) {
        String lock = LOCK_PREFIX + key;
        return redisTemplate.delete(lock);
    }

    public Boolean delete(String key) {
        return redisTemplate.delete(key);
    }

    public void delete(String... key) {
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key));
            }
        }
    }

    public Long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    public Boolean expire(String key, long time) {
        return time > 0 ? redisTemplate.expire(key, time, TimeUnit.SECONDS) : Boolean.FALSE;
    }

    public Boolean hasKey(String key) {
        return redisTemplate.hasKey(key);
    }

    public void deleteByPattern(String pattern) {
        Set<String> keys = redisTemplate.keys(pattern);
        if (!ObjectUtils.isEmpty(keys)) {
            redisTemplate.delete(keys);
        }
    }

    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    public void set(String key, Object value) {
        redisTemplate.opsForValue().set(key, value);
    }

    public long incr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }

    public long decr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().decrement(key, delta);
    }

    /**
     * ********************************** set相关 ********************************
     */
    public Set<Object> sMembers(String key) {
        return redisTemplate.opsForSet().members(key);
    }

    public Boolean sIsMember(String key, Object value) {
        return redisTemplate.opsForSet().isMember(key, value);
    }

    public Long sAdd(String key, Object... values) {
        return redisTemplate.opsForSet().add(key, values);
    }

    public Long sAdd(String key, long time, Object... values) {
        Long count = redisTemplate.opsForSet().add(key, values);
        if (time > 0) {
            expire(key, time);
        }
        return count;
    }

    public Long sSize(String key) {
        return redisTemplate.opsForSet().size(key);
    }

    public Long sRemove(String key, Object... values) {
        return redisTemplate.opsForSet().remove(key, values);
    }

    /**
     * ********************************** list相关 ********************************
     */
    public List<Object> lRange(String key, long start, long end) {
        return redisTemplate.opsForList().range(key, start, end);
    }

    public Long lSize(String key) {
        return redisTemplate.opsForList().size(key);
    }

    public Object lIndex(String key, long index) {
        return redisTemplate.opsForList().index(key, index);
    }

    public Long lRightPush(String key, Object value) {
        return redisTemplate.opsForList().rightPush(key, value);
    }

    public Long lRightPush(String key, Object value, long time) {
        Long r = redisTemplate.opsForList().rightPush(key, value);
        if (time > 0) {
            expire(key, time);
        }
        return r;
    }

    public Long lRightPushAll(String key, List value) {
        return redisTemplate.opsForList().rightPushAll(key, value);
    }

    public Long lRightPushAll(String key, List<Object> value, long time) {
        Long r = redisTemplate.opsForList().rightPushAll(key, value);
        if (time > 0) {
            expire(key, time);
        }
        return r;
    }

    public void lSet(String key, long index, Object value) {
        redisTemplate.opsForList().set(key, index, value);
    }

    public Long lRemove(String key, long count, Object value) {
        return redisTemplate.opsForList().remove(key, count, value);
    }

    /**
     * ********************************** hash相关 ********************************
     */
    public void hPut(String key, String hashKey, Object value) {
        redisTemplate.opsForHash().put(key, hashKey, value);
    }

    public Object hGet(String key, String hashKey) {
        return redisTemplate.opsForHash().get(key, hashKey);
    }

    public Long hDelete(String key, Object... hashKey) {
        return redisTemplate.opsForHash().delete(key, hashKey);
    }

    public Long hSize(String key) {
        return redisTemplate.opsForHash().size(key);
    }

    /**
     * 向hash中存储一个指定key的map，hash中存在的会被覆盖修改，不存在的会新增
     *
     * @param key
     * @param map
     */
    public void hPutAll(String key, Map<Object, Object> map) {
        redisTemplate.opsForHash().putAll(key, map);
    }

    /**
     * 查询指定key的hash表
     *
     * @param key
     * @return 如果key不存在，则返回size为0的map对象
     */
    public Map<Object, Object> hEntries(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * ********************************** zset相关 ********************************
     *
     * @see <a href="http://redis.io/commands/zadd">Redis Documentation: ZADD</a>
     */
    public Boolean zAdd(String key, Object value, double score) {
        return redisTemplate.opsForZSet().add(key, value, score);
    }

    /**
     * 获取zset排序好的元素，排位为升序排列
     *
     * @param key
     * @param start 开始位置，包含此位置
     * @param end   结束位置，包含此位置
     * @return
     * @see <a href="http://redis.io/commands/zrange">Redis Documentation: ZRANGE</a>
     */
    public Set<Object> zRange(String key, long start, long end) {
        return redisTemplate.opsForZSet().range(key, start, end);
    }

    public Set<Object> zRangeByScore(String key, double min, double max) {
        return redisTemplate.opsForZSet().rangeByScore(key, min, max);
    }

    public Map<String, Double> zRangeByScoreWithScores(String key, double min, double max) {
        Set<ZSetOperations.TypedTuple<Object>> set = redisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max);
        Map<String, Double> result = new HashMap<>();
        for (ZSetOperations.TypedTuple<Object> tt : set) {
            result.put(tt.getValue().toString(), tt.getScore());
        }
        return result;
    }

    /**
     * @param key
     * @param value
     * @param delta
     * @return
     * @see <a href="http://redis.io/commands/zincrby">Redis Documentation: ZINCRBY</a>
     */
    public Double zIncrementScore(String key, Object value, double delta) {
        return redisTemplate.opsForZSet().incrementScore(key, value, delta);
    }

    public Double zScore(String key, Object value) {
        return redisTemplate.opsForZSet().score(key, value);
    }

    public Long zRemove(String key, Object... values) {
        return redisTemplate.opsForZSet().remove(key, values);
    }

    public Long zSize(String key) {
        return redisTemplate.opsForZSet().size(key);
    }
}
